# [JS进阶]执行上下文、变量提升、函数提升
# 开题
看具体内容前,不妨让我们先来做下下面的一个例题,并留下你的答案:
//实例一
var foo = function () {
console.log('foo1');
console.log(a);
var a = 1;
console.log(a);
function a(){}
console.log(a);
}
foo();
var foo = function () {
console.log('foo2');
console.log(a);
function a(){}
console.log(a);
var a = 1;
console.log(a);
}
foo();
//实例二
function foo() {
console.log('foo1');
}
foo();
function foo() {
console.log('foo2');
}
foo();
下面将会给出实例一和实例二的输出结果,如果你的答案和其中的有出入,那么这篇文章可能就是你需要的;如果你的答案和给出的一模一样,建议也可以的读一读这篇文章,看看知识点和思路是否一致!
例题答案:
//实例一
foo1
a(){}
1
1
foo2
a(){}
a(){}
1
//实例二
foo2
foo2
# 执行上下文
执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行。 JavaScript 引擎并非一行一行地分析和执行程序,而是一段一段地分析执行。当执行一段代码的时候,会进行一个“准备工作”,比如例子中的变量提升、函数提升;
# 分类
- 全局执行上下文 :任何不在函数内部的代码都在全局上下文中(全局作用域中)。
- 函数执行上下文 :当一个函数被调用时, 都会为该函数创建一个新的上下文(函数作用域中)。
- eval 函数执行上下文 : 执行在 eval 函数内部的代码它属于自己的执行上下文。(开发中不常用不做讨论)
# 执行栈
在js中,用执行栈对js执行上下文进行管理,它符合栈的特征——LIFO(后进先出);在js代码执行过程中,按照代码段执行顺序将执行上下文压入栈中,基于栈中顺序根据代码段完成时间先后从栈中释放出来。
let a = 1;
function first() {
console.log(2);
second();
console.log(3);
}
function second() {
console.log(4);
}
first();
console.log(5);
- 当上述代码在浏览器加载时,JavaScript 引擎创建了一个全局执行上下文并把它压入当前执行栈。当遇到 first() 函数调用时,JavaScript 引擎为该函数创建一个新的执行上下文并把它压入当前执行栈的顶部。
- 当从 first() 函数内部调用 second() 函数时,JavaScript 引擎为 second() 函数创建了一个新的执行上下文并把它压入当前执行栈的顶部。当 second() 函数执行完毕,它的执行上下文会从当前栈弹出,并且控制流程到达下一个执行上下文,即 first() 函数的执行上下文。
- 当 first() 执行完毕,它的执行上下文从栈弹出,控制流程到达全局执行上下文。一旦所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文。
看到这里我们就明白为什么js中代码是一段一段执行的,为什么执行的顺序是按照调用先后来的;
# 变量提升
在讨论变量提升的时候,我们应该明确一点:变量提升是只针对 var 声明的变量而言的,对于ES6新特性 let 和const 声明的变量不存在变量提升(但是要注意 暂时性死区 这个概念,此处不过多讲解)!
通过一个简单例子进行分析:
var foo = function () {
console.log(a);
var a = 1;
console.log(a);
}
foo();
输出结果:
undefined
1
分析结果我们不难发现,在函数中第一个console.log
在代码var a = 1
之前,按理说应该报错;实则不然,对于var
声明的变量存在着变量提升,如果var
声明和赋值一起进行,会将声明提升到所在作用域的最顶层,赋值操作保持在原来的位置!
上述例子可以变形为:
var foo = function () {
var a; // var只声明,默认值为undefined
console.log(a); // undefined
a = 1;
console.log(a); // 1
}
foo();
# 函数提升
函数的提升和变量的提升类似,都是提升到作用域的最开始的位置,只不过变量的提升是分两步的,第一步是变量声明的提升,第二步是变量的赋值。而函数的提升是直接将整个函数整体提升到作用域的最开始位置,相当于剪切过去。 如果一个作用域中同时存在函数的提升和变量的提升,变量提升高于函数提升。也就是说变量提升的声明代码在函数提升代码前面: 对实例进行变形:
//实例一提升变形
var foo;
var foo;
foo = function () {
var a;
function a(){};
console.log('foo1'); // foo1
console.log(a); // a(){}
a = 1;
console.log(a); // 1
console.log(a); // 1
}
foo();
foo = function () {
var a;
function a(){};
console.log('foo2'); //foo2
console.log(a); // a(){}
console.log(a); // a(){}
a = 1;
console.log(a); // 1
}
foo();
- 实例一中顶层中是以变量声明的方式存放函数,所以顶层还是函数提升,声明提前,赋值位置不变;
//实例二提升变形
function foo() {
console.log('foo1');
}
function foo() {
console.log('foo2');
}
foo(); // foo2
foo(); // foo2
- 实例二中属于函数提升,第二次的
foo
函数声明会覆盖第一次的,所有调用只会返回第二次调用的执行;